﻿using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Net;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Wulong.Phone.Utils.Rx;

namespace Wulong.Phone.Utils
{
    public static class ObservableResource
    {
        private const string CacheFolder = "cache";

        public static void CacheResource(MemoryStream toCache, string cacheKey)
        {
            string targetFile = Path.Combine(CacheFolder, cacheKey);
            using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
            {
                if (!isoFile.DirectoryExists(CacheFolder))
                    isoFile.CreateDirectory(CacheFolder);
                if (isoFile.FileExists(targetFile))
                    isoFile.DeleteFile(targetFile);
                using (IsolatedStorageFileStream outputStream = isoFile.CreateFile(targetFile))
                {
                    toCache.WriteTo(outputStream);
                }
            }
        }

        public static IObservable<MemoryStream> FromCache(string cacheKey)
        {
            return new AnonymousObservable<MemoryStream>(observer =>
                                                             {
                                                                 string targetFile = Path.Combine(CacheFolder, cacheKey);
                                                                 using (
                                                                     IsolatedStorageFile isoFile =
                                                                         IsolatedStorageFile.GetUserStoreForApplication()
                                                                     )
                                                                 {
                                                                     if (!isoFile.FileExists(targetFile))
                                                                     {
                                                                         observer.OnNext(null);
                                                                     }
                                                                     else
                                                                     {
                                                                         using (
                                                                             var inputStream =
                                                                                 isoFile.OpenFile(targetFile,
                                                                                                  FileMode.Open,
                                                                                                  FileAccess.Read))
                                                                         {
                                                                             observer.OnNext(
                                                                                 inputStream.ToMemoryStream());
                                                                         }
                                                                     }
                                                                 }
                                                                 observer.OnCompleted();
                                                                 return Disposable.Empty;
                                                             });
        }

        public static IObservable<MemoryStream> FromInternet(Uri onlineResource)
        {
            return new AnonymousObservable<WebResponse>(observer =>
                                                            {
                                                                var httpWebRequest =
                                                                    (HttpWebRequest) WebRequest.Create(onlineResource);
                                                                httpWebRequest.BeginGetResponse(iar =>
                                                                                                    {
                                                                                                        WebResponse
                                                                                                            response;
                                                                                                        try
                                                                                                        {
                                                                                                            var
                                                                                                                requestState
                                                                                                                    =
                                                                                                                    (
                                                                                                                    HttpWebRequest
                                                                                                                    )
                                                                                                                    iar.
                                                                                                                        AsyncState;
                                                                                                            response =
                                                                                                                requestState
                                                                                                                    .
                                                                                                                    EndGetResponse
                                                                                                                    (iar);
                                                                                                        }
                                                                                                        catch (
                                                                                                            Exception
                                                                                                                exception
                                                                                                            )
                                                                                                        {
                                                                                                            observer.
                                                                                                                OnError(
                                                                                                                    exception);
                                                                                                            return;
                                                                                                        }
                                                                                                        observer.OnNext(
                                                                                                            response);
                                                                                                        observer.
                                                                                                            OnCompleted();
                                                                                                    }, httpWebRequest);
                                                                return Disposable.Empty;
                                                            })
                .Select(response => response.GetResponseStream().ToMemoryStream());
        }

        public static IObservable<MemoryStream> CacheAsFallback(Uri onlineResource, string cacheKey)
        {
            IObservable<MemoryStream> observableOnlineResource = FromInternet(onlineResource);
            return observableOnlineResource.Select(stream =>
                                                       {
                                                           CacheResource(stream, cacheKey);
                                                           stream.Position = 0;
                                                           return stream;
                                                       })
                .Catch<MemoryStream, Exception>(exception =>
                                                FromCache(cacheKey).Select(cacheStream =>
                                                                               {
                                                                                   if (cacheStream == null)
                                                                                       throw new Exception("No cache");
                                                                                   return cacheStream;
                                                                               }));
        }
    }
}